/**
 \file firmware.pde
 \brief Domino (OpenDomo for Arduino)
 ****************************************************************************
 *  This file is part of the OpenDomo project.
 *  Copyright(C) 2011 OpenDomo Services S.L.
 *
 *  Oriol Palenzuela Roses <opalenzuela (at) opendomo (dot) com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************
 \mainpage Domino
 Domino is a multi-purpose driver, designed for creating quick prototypes and
 automated products using a normalized configuration syntax.

 A detailed explanation of all the supported commands and architecture can be
 found in the following webpages:

 -# http://es.opendomo.org/domino (Main page)
 -# http://es.opendomo.org/domino_reference (Reference manual)

 This document is only intended to be useful for the firmware's programmers.
*/


/// Current library version
#define VERSION "0.5.2"
// Library for managing the configuration storage
//#include <EEPROM.h>
// Compulsory system libraries
#include <avr/pgmspace.h>

// Library for serial communication (required by Ethernet.h)
#include <SPI.h>
// Library for using an Ethernet Shield as host connection
//#include <Server.h>
//#include <Client.h>
#include <Ethernet.h>

#include <avr/eeprom.h>

//! Possible outputs for a \ref writef
enum outputChannel {
	NONE = 0,
	SERIALPORT = 1,
	TELNET = 2,
	HTTP = 3
} output;

// Configuration file and function prototypes
#include "domino.h"


/** \page EEPROM EEPROM Map
 The EEPROM in Arduino Duemilanove with ATMEGA328 has 1024Bytes availables.
 Domino uses this space to store all the configuration variables required,
 organized in the following segments:

 -# Ports configuration: starting from the first byte (\ref EMPORTSOFFSET), with
	20 Bytes (\ref EMPORTSLOT) for each port, it contains the alias and type of
	each port.
 -# Links: starting after the ports segment (\ref EMLINKSOFFSET) contains the
	index of two ports and a third byte for the type.
 -# Virtual port information: starting from \ref EMVPORTOFFSET, contains the
	data of extended ports (virtual and grouped ports)
 -# Network information: starting from \ref EMNETCFOFFSET, contains 20 bytes
	with the network information (MAC(6) + IP(4) + GW(4) + MASK(4))
 -# Board information: starting in \ref EMBOARDOFFSET contains some information
        about the board, like the name
 -# On-board functions: in \ref EMFUNCSOFFSET contains software-programmable
	\ref Func embedded in the board

*/


// Iterate for each link
//#define FOREACHLINK(i)  for (i=0;i<MAXLINKS;i++)
//! Delete the specified link
#define emptyLink(i) {links[i][0]=0; links[i][1]=0; links[i][2]=0;}


// lnk puls1 alarm
// 012345678901234
//! Copy the first argument into the string
#define GETARG1(str,arg1) arg1[0]=str[4];arg1[1]=str[5];arg1[2]=str[6];\
	arg1[3]=str[7];arg1[4]=str[8];arg1[5]=0;
//! Copy the second argument into the string
#define GETARG2(str,arg2) arg2[0]=str[10];arg2[1]=str[11];arg2[2]=str[12];\
	arg2[3]=str[13];arg2[4]=str[14];arg2[5]=0;
//! Return TRUE if the byte is a valid character (a-z, A-Z and 0-9)
#define ISVALIDCHAR(a) ((a>=48 && a<=57) || (a>=65 && a<=90) || (a>=97 && a<=122))

#ifdef NO_PDE
extern "C" void __cxa_pure_virtual()
{
}
#endif

//! Cycles during this second
byte cps;
//! Board name
char bname[6];
//! Link table (3 bytes: port1, port2 and type)
byte links[MAXLINKS][3];
//! Last link created or referenced
byte lastlink = 0;

//! This flag determines if the network is enabled or not
//byte network = 0;

// {{{ Flash Strings
prog_char strfmt_std_num[] PROGMEM = "%c:%s.%s %s %d\n";
prog_char strfmt_std[] PROGMEM   = "%c:%s.%s %s %s\n";
prog_char strfmt_lnk[] PROGMEM   = "%c:%s %s-%s %c\n";
prog_char strfmt_time[] PROGMEM  = "%c:%s dtime %d:%d:%d\n";
//prog_char strfmt_value_off[] PROGMEM = "%c:%s.%s value OFF\n";
prog_char strfmt_trgrd[] PROGMEM = "%c:%s.%s trgrd\n";
prog_char strfmt_value[] PROGMEM = "%c:%s.%s value %d\n";
prog_char strfmt_ethip[] PROGMEM = "%c:%s.ethip \n";
prog_char strfmt_ethmc[] PROGMEM = "%c:%s.ethmc \n";
prog_char strfmt_ethmk[] PROGMEM = "%c:%s.ethmk \n";
prog_char strfmt_ethgw[] PROGMEM = "%c:%s.ethgw \n";
prog_char strfmt_hiber[] PROGMEM = "%c:%s.hiber\n";
//prog_char strfmt_upsec[] PROGMEM = "%c:%s.upsec %d\n";
//prog_char strfmt_upday[] PROGMEM = "%c:%s.upday %d\n";
//prog_char strfmt_frmem[] PROGMEM = "%c:%s.frmem %d\n";
//prog_char strfmt_objsz[] PROGMEM = "%c:%s.objsz %d\n";
prog_char strfmt_procd[] PROGMEM = "%c:%s.procd %d\n";
prog_char strfmt_cfloa[] PROGMEM = "%c:%s.board cfloa\n";
prog_char strfmt_ncfdef[] PROGMEM = "N:%s cfdef\n";
prog_char strfmt_cmdok[] PROGMEM = "N:board cmdok";
prog_char strfmt_error[] PROGMEM = "E:board error ";

//prog_char str_on[] PROGMEM = "ON";
//prog_char str_off[] PROGMEM = "OFF";

prog_char str_upsec[] PROGMEM = "upsec";
prog_char str_upday[] PROGMEM = "upday";

prog_char str_versn[] PROGMEM = "versn";
prog_char str_bdate[] PROGMEM = "bdate";
prog_char str_build[] PROGMEM = "build";

prog_char str_mfree[] PROGMEM = "mfree";
prog_char str_mallo[] PROGMEM = "mallo";
prog_char str_mused[] PROGMEM = "mused";

prog_char str_value[] PROGMEM = "value";
prog_char str_mxlvl[] PROGMEM = "mxlvl";
prog_char str_mnlvl[] PROGMEM = "mnlvl";
prog_char str_domin[] PROGMEM = "domin";
prog_char str_empty[] PROGMEM = "empty";
prog_char str_almax[] PROGMEM = "almax";
prog_char str_almin[] PROGMEM = "almin";
prog_char str_toler[] PROGMEM = "toler";
prog_char str_wnmax[] PROGMEM = "wnmax";
prog_char str_wnmin[] PROGMEM = "wnmin";
prog_char str_dnetmc[] PROGMEM = "D:netmc:";
prog_char str_inotini[] PROGMEM = "I:netoff\n";
//prog_char str_ndbgoff[] PROGMEM = "N:debug off\n";
//prog_char str_ndbgon[] PROGMEM = "N:debug on\n";
//prog_char str_nechooff[] PROGMEM = "N:echo off\n";
//prog_char str_nechoon[] PROGMEM = "N:echo on\n";
//prog_char str_nnicok[] PROGMEM = "N:nicok";
// }}}


char instruction[BUFFERSIZE];	///< Command received
byte inspos = 0;		///< Command byte position
bool echo = false;		///< Flag that defines if the local echo is activated
bool debug = false;		///< Flag that defines if the board operates in debug mode
byte alarmport = 0;		///< Alarm port configured (0 if none)
byte pulseport = 0;		///< Pulse port configured (0 if none)

short regP = 0;			///< Program pointer for \ref Func
int regM = 0;                   ///< Main register for \ref Func

///! Seconds in a day
#define DAYLENGHT 86400
unsigned int days = 0;		///< Uptime days
byte hours = 0;         ///< Uptime hours
byte minutes = 0;       ///< Uptime minutes
byte seconds = 0;	///< Uptime seconds


void print_error(byte out, int code) 
{
   	char buffer[16];
 	flstrn(strfmt_error,buffer,16);
	switch (out) 
	{
	case TELNET:
		#ifdef MODULE_TELNET
		Telnet.print(buffer);
		Telnet.println(code);
		#endif
		break;
	case SERIALPORT:
		Serial.print(buffer);
		Serial.println(code);
		break;
	}
}

void print_cmdok(byte out)
{
 	char buffer[16];
 	flstrn(strfmt_cmdok,buffer,16);
	switch (out) 
	{
	case TELNET:
		#ifdef MODULE_TELNET
		Telnet.println(buffer);
		#endif
		break;
	case SERIALPORT:
		Serial.println(buffer);
		break;
	}
}



// {{{ freeMemory(): return the number of bytes currently free in RAM
extern int __bss_end;
extern int *__brkval;
int freeMemory()
{
	int free_memory;
	if ((int)__brkval == 0)
		free_memory = ((int)&free_memory) - ((int)&__bss_end);
	else
		free_memory = ((int)&free_memory) - ((int)__brkval);
	return free_memory;
}
// }}} 

int freeMalloc() {
  int size = 2000; // Use 2048 with ATmega328
  byte *buf;

  while ((buf = (byte *) malloc(--size)) == NULL)
    ;

  free(buf);

  return size;
}


// {{{ printVersion(): Print the board version to the standard output
void printVersion()
{
	char buffer[10];
	char format[16];
        flstrn(strfmt_std,format,16);
	writef(output, format, INFO, bname, flstrn(str_versn,buffer,10), VERSION, "");
	writef(output, format, INFO, bname, flstrn(str_bdate,buffer,10), __DATE__, "");
	writef(output, format, INFO, bname, flstrn(str_build,buffer,10), __TIME__, "");
}
// }}}
void printUptime(){
	char buffer[10];
	char format[16];
        flstrn(strfmt_std_num,format,16);
	writef(output, format, INFO, bname, flstrn(str_upsec,buffer,10), "", int(millis()/1000));
//	writef(output, format, INFO, bname, flstrn(str_upday,buffer,10), "", minutes);
//	writef(output, format, INFO, bname, flstrn(str_upday,buffer,10), "", days);
}
void printDatetime(){
	char format[16];
        flstrn(strfmt_time,format,16);
	writef(output, format, INFO, bname, hours, minutes, seconds);
//	writef(output, format, INFO, bname, flstrn(str_upday,buffer,10), "", minutes);
//	writef(output, format, INFO, bname, flstrn(str_upday,buffer,10), "", days);
}
void printMemory(){
	char buffer[10];
	char format[16];
        flstrn(strfmt_std_num,format,16);
	writef(output, format, INFO, bname, flstrn(str_mfree,buffer,10), "", freeMemory());
	writef(output, format, INFO, bname, flstrn(str_mallo,buffer,10), "", freeMemory());
	writef(output, format, INFO, bname, flstrn(str_mused,buffer,10), "", sizeof(ports));
}



// {{{ itoan(): integer to string
/** Transforms a integer into a string with lenght LEN
 \param val Integer value
 \param result String where the result will be written
 \param len Lenght of the string
 \return true if the operation was succeeded
*/
char *itoan(int val, char *result, byte len)
{
	int i = val;
	byte c = 0;
	len--;

	for (i = 0; i <= len; i++)
		result[i] = '0';	//Rellenamos con ceros

	while (i > 0) {
		c++;
		i /= 10;
	}

	if (c > len)
		return result;	// Too less bytes to represent

	result[len] = 0;
	c = len;
	i = val;
	while (i > 0) {
		int digit = i - 10 * (i / 10);
		i /= 10;
		result[--c] = digit + 48;
	}
	return result;
}

// }}}

// {{{ h2d(): hexadecimal chars to decimal
/** Transforms a 2 bytes hexadecimal string into its integer value
  Example: h2d(E,F)=239
 \param a First byte of the source string
 \param b Second byte of the source string
 \return The integer value
*/
byte h2d(char a, char b)
{
	if (a >= 65 && a <= 70)
		a = a - 55;
	else
		a = a - 48;
	if (b >= 65 && b <= 70)
		b = b - 55;
	else
		b = b - 48;
	return (a * 16 + b);
}

// }}}



/** This function triggers an event if a port value has changed.
 \param port Port number
 \param ov Old value of the port
 \param nv New value of the port
*/
void triggerPortChange(byte port, byte ov, byte nv)
{
	//byte i;
	char buffer[20];
	char format[20];
        flstrn(strfmt_std_num,format,20);
	char pname[6];
	int i;

	if (ov == nv)
		return;		// Value didn't change

	ports[port].changes++;
	eeprom_get_str(pname, port*EMPORTSLOT, 6);

	// Comprobamos rangos máximos y mínimos para los puertos de entrada analógicos
	if (ISINPUT(port) && ISANALOG(port)) {
  	//if (ports[port].type =='a') {
    	//	Serial.print("D:");
	//	Serial.print(pname);
	//	Serial.print(ov,DEC);
	//	Serial.print(" --> ");
	//	Serial.println(nv,DEC);
		// Si el valor supera el limite impuesto, y esta subiendo, disparar alarma
		//i = port-DIGITALPORTS;
#ifdef MODULE_RANGES
		if (abs(nv - ov) >= ranges[port-DIGITALPORTS].tolerance) {
			eeprom_get_str(pname, port*EMPORTSLOT, 6);
			if (output!=HTTP) writef(output, format,
			       NOTICE, bname, pname, 
				flstrn(str_value,buffer,10), nv);
		}
		// If the value enters the Alarm area, put the alarmport to HIGH
		if ((ranges[port-DIGITALPORTS].alarmMax < 1024)
		    && (nv > ranges[port-DIGITALPORTS].alarmMax) && (nv > ov)) {
			if (output!=HTTP) writef(output, format,
			       ALARM, bname, pname, 
				flstrn(str_mxlvl,buffer,10), nv);
			if (alarmport>0) setPortValue(alarmport, HIGH);
		}
		// If the value is entering the warning area ...
		if ((ranges[port-DIGITALPORTS].warningMax < 1024)
		    && (nv > ranges[port-DIGITALPORTS].warningMax) && (nv > ov)) {
  			// ... notify it! ...
			if (output!=HTTP) writef(output, format,
				WARNING, bname, pname, 
				flstrn(str_mxlvl,buffer,10), nv);
			// ... and if there is a warning link ...
			for (i=0;i<MAXLINKS;i++){
				if (links[i][0]==port && links[i][2]=='W'){
  					// ... put it to HIGH
					setPortValue(links[i][1],HIGH);
				}  
			}
		}
		// If the value is over Alarm area, put the alarmport to HIGH
		if ((ranges[port-DIGITALPORTS].alarmMin > 0)
		    && (nv < ranges[port-DIGITALPORTS].alarmMin) && (nv < ov)) {
			if (output!=HTTP) writef(output, format,
				ALARM, bname, pname, 
				flstrn(str_mnlvl,buffer,10), nv);
			if (alarmport>0) setPortValue(alarmport, HIGH);
		}
		// If the value is entering the warning area ...
		if ((ranges[port-DIGITALPORTS].warningMin > 0)
		    && (nv < ranges[port-DIGITALPORTS].warningMin) && (nv < ov)) {
  			// ... notify it! ...
			if (output!=HTTP) writef(output, format,
				WARNING, bname, pname, 
				flstrn(str_mnlvl,buffer,10), nv);
			// ... and if there is a warning link ...
			for (i=0;i<MAXLINKS;i++){
				if (links[i][0]==port && links[i][2]=='w'){
  					// ... put it to HIGH
					setPortValue(links[i][1],HIGH);
				}  
			}
		}
		// If value is inside the ranges
		if (nv > ranges[port-DIGITALPORTS].warningMin && nv < ranges[port-DIGITALPORTS].warningMax){
			// ... there is a warning link ...
  			for (i=0;i<MAXLINKS;i++){
    				// ... and it's activated ...
				if (links[i][0]==port && (links[i][2]=='W'||links[i][2]=='w') 
					&& ports[links[i][1]].value == HIGH){
  					// ... bring it back to LOW
					setPortValue(links[i][1],LOW);
				}  
			}			
		}
#endif
	}
	if (ISINPUT(port) && ISDIGITAL(port)) {
		// Si un puerto digital ha cambiado y tiene un temporizador asignado, le
		// asignamos al contador el valor correspondiente.
//      if (t_len[port]>0 && t_count[port]==0 && nv==HIGH) t_count[port] = t_len[port];
		flstrn(strfmt_std,format,20);
		if (output!=HTTP) writef(output,format, 
			NOTICE, bname,pname,flstrn(str_value,buffer,10), (nv==1)?ON:OFF);
	}

	for (i = 0; i < MAXLINKS; i++) {
		if (links[i][0] == port)	// Puerto enlazado
		{
			eeprom_get_str(pname, links[i][1]*EMPORTSLOT, 6);
//			writef(output, flstr(strfmt_trgrd), NOTICE, bname, pname);
			switch (links[i][2]) {
			case 'd':	// Directo: si A=1, B=1. Si A=0, B=0. Si A=65%, B=65%
				setPortValue(links[i][1], nv);
				break;
			case 'i':	// Inverso: si A=1, B=0. Si A=0, B=1. Si A=65%, B=35%
				if (nv == LOW)
					setPortValue(links[i][1], HIGH);
				else if (nv == HIGH)
					setPortValue(links[i][1], LOW);
				/// @todo Activate inverted link for analog ports
				//else
				//      setPortValue(links[i][1],255-nv);
				break;
			case 'p':	// Pulso: 0->1 o 1->0 cuando el puerto pasa a HIGH
				if (ISDIGITAL(port) && nv == HIGH) {
					if (ports[links[i][1]].value == LOW)
						setPortValue(links[i][1], HIGH);
					else
						setPortValue(links[i][1], LOW);
				}
				break;
			case 'f':	// Fall: 0->1 o 1->0 cuando el puerto pasa a LOW
				if (ISDIGITAL(port) && nv == LOW) {
					if (ports[links[i][1]].value == LOW)
						setPortValue(links[i][1], HIGH);
					else
						setPortValue(links[i][1], LOW);
				}
				break;
			case 'W':
			case 'w':
				/// Nothing. Done before
				break;
			default:
//				writef(output, flstr(strfmt_error),
//				       ERROR, bname, pname, 304);
				print_error(output,304);
//				if (debug == 1)
//					writef(output, "%c:%s %c",
//					       DEBUG, (char *)"lnktype",
//					       links[i][2]);
				break;

			}
		}
	}
}

// {{{ setPortValue(): set port value
/** Set a value for the specified port
 \param i Port index (see \ref getPortId)
 \param value New value
 \return 0 if the value could be assigned, other if not.
*/
int setPortValue(unsigned char i, int value)
{
	char pname[6];
	char buffer[20];
	eeprom_get_str(pname, i*EMPORTSLOT, 6);

	if (ISINPUT(i)) {
//		writef(output, flstr(strfmt_error), ERROR, bname, pname, 107);
		print_error(output,107);
		return false;
	}

	else if (ISDIGITAL(i)) {
		digitalWrite(i, value);
		ports[i].value = value;
		if (output<HTTP) 
			writef(output, flstrn(strfmt_std,buffer,20), NOTICE, bname, pname, "value", value?ON:OFF,"");
	} else if (ISANALOG(i)) {
		analogWrite(i, value);
		ports[i].value = value;
		if (output<HTTP) 
			writef(output, flstrn(strfmt_std_num,buffer,20), NOTICE, bname, pname, "value", value);
/*	} else if (ISVIRTUAL(i)) {
		triggerPortChange(i, LOW, HIGH); */
	} else if (ISDISABLED(i)) {
		// Nothing. No warning neither
	} else {
		print_error(output,103);
		return 1;
	}
	//print_cmdok(output);
	//writef(output, flstr(strfmt_cmdok), NOTICE, bname, pname);
	return true;
}

// }}}

// {{{ resetPorts()
/** Set the type and configured characteristics to each port
*/
void resetPorts()
{
	unsigned char i;
	for(i=0;i<TOTALPORTS;i++) {
		/// @todo Alta impedancia if type = "-"
		if (ISINPUT(i)) {
			pinMode(i, INPUT);
			digitalWrite(i, HIGH);	// Activating internal pull-up resistor

			if (ISDIGITAL(i))
				ports[i].value = digitalRead(i);

			if (ISANALOG(i))
				ports[i].value = 0;
			//(analogRead(i-DIGITALPORTS) * 2.5 * ranges[i-DIGITALPORTS].fxB / 1023 ) - ranges[i-DIGITALPORTS].fxA;
		} else if (ISOUTPUT(i)) {
			digitalWrite(i, LOW);	// deactivating pull-up
			pinMode(i, OUTPUT);
		}
		if (ISPULSE(i)) {
			//pinMode(i, INPUT);
			pulseport = i;
			//noInterrupts();

#ifdef MODULE_IR
			if (i == 2)
				attachInterrupt(0, processIR, CHANGE);
			if (i == 3)
				attachInterrupt(1, processIR, CHANGE);
#endif
		}
		//! @todo Enable wake up with attachInterrupt(0, wakeUp, LOW);
	}
}

// }}}

/** Store the specified name of the board in the EEPROM
 \param name New board name (5 Bytes)
 \return Always 0
*/
int saveBoardName(char *name)
{
	strlcpy(bname, name, 6);
	eeprom_set_str(EMBOARDOFFSET, name, 5);
	return true;
}

/// Load the configuration from the EEPROM
int loadConfig()
{
	unsigned char x;
//	int y;
	short dir;

	// Read the board's name (and set a default one if missing)
	eeprom_get_str(bname, EMBOARDOFFSET, 6);

	for(x=0;x<TOTALPORTS;x++) {
		dir = int (EMPORTSOFFSET + x * EMPORTSLOT);
		ports[x].type = eeprom_get_byte(dir + 5);

		if (!ISVALIDCHAR(ports[x].type))
			ports[x].type = 'x';

#ifdef MODULE_RANGES
		if (x>=DIGITALPORTS && x<DIGITALPORTS+ANALOGPORTS) {
			// For analog ports load RANGE
			ranges[x-DIGITALPORTS].alarmMin = eeprom_get_ushort(dir + 6);
			ranges[x-DIGITALPORTS].warningMin = eeprom_get_ushort(dir + 8);
			ranges[x-DIGITALPORTS].warningMax = eeprom_get_ushort(dir + 10);
			ranges[x-DIGITALPORTS].alarmMax = eeprom_get_ushort(dir + 12);
			ranges[x-DIGITALPORTS].tolerance = eeprom_get_byte(dir + 14);
			// Conversion f(x) = A + Bx;
			ranges[x-DIGITALPORTS].fxA = eeprom_get_ushort(dir + 16);
			ranges[x-DIGITALPORTS].fxB = eeprom_get_ushort(dir + 18);

			debug_write(x);
			debug_write(ranges[x-DIGITALPORTS].fxA);
			debug_write(ranges[x-DIGITALPORTS].fxB);

			// Si la placa no ha sido configurada, asignamos valores maximos
			if (ranges[x-DIGITALPORTS].alarmMax == 0)
				ranges[x-DIGITALPORTS].alarmMax = 1023;
			if (ranges[x-DIGITALPORTS].warningMax == 0)
				ranges[x-DIGITALPORTS].warningMax = 1023;

		}
#endif
		if (x<DIGITALPORTS && (eeprom_get_byte(dir + 7) == 'a'))
			alarmport = x;
	}

	for (x = 0; x < MAXLINKS; x++) {
		links[x][0] = eeprom_get_byte(EMLINKSOFFSET + x * 5);
		links[x][1] = eeprom_get_byte(EMLINKSOFFSET + x * 5 + 1);
		links[x][2] = eeprom_get_byte(EMLINKSOFFSET + x * 5 + 2);

		if (links[x][0] > TOTALPORTS)
			links[x][0] = 0;
		if (links[x][1] > TOTALPORTS)
			links[x][1] = 0;
		if (!ISVALIDCHAR(links[x][2]))
			links[x][2] = 'x';
	}

	return true;
}

/// Save the configuration into the EEPROM (see \ref EEPROM Map)
int saveConfig()
{
	unsigned char x;
//	int y;
	short dir;
//	unsigned char b;

	for(x=0;x<TOTALPORTS;x++) {
		dir = int (EMPORTSOFFSET + x * EMPORTSLOT);
		eeprom_set_byte(dir + 5, ports[x].type);

#ifdef MODULE_RANGES
		if (x>=DIGITALPORTS && x<DIGITALPORTS+ANALOGPORTS) {
			eeprom_set_ushort(dir + 6, ranges[x-DIGITALPORTS].alarmMin);
			eeprom_set_ushort(dir + 8, ranges[x-DIGITALPORTS].warningMin);
			eeprom_set_ushort(dir + 10, ranges[x-DIGITALPORTS].warningMax);
			eeprom_set_ushort(dir + 12, ranges[x-DIGITALPORTS].alarmMax);
			eeprom_set_byte(dir + 14, ranges[x-DIGITALPORTS].tolerance);
			eeprom_set_ushort(dir + 16, ranges[x-DIGITALPORTS].fxA);
			eeprom_set_ushort(dir + 18, ranges[x-DIGITALPORTS].fxB);
		}
#endif
/*
		if (ports[x].extra!=NULL) {
			EW(dir + 16, ports[x].extra->param1);
			EW(dir + 17, ports[x].extra->param2);
			EW(dir + 18, ports[x].extra->param3);
		} 
*/
		

		if (x<DIGITALPORTS){
			if ((x == alarmport) && (alarmport!=0)) {
				eeprom_set_byte(dir + 7, 'a');
			} else {
				eeprom_set_byte(dir + 7, ' ');
			}
		}
	}
	for (x = 0; x < MAXLINKS; x++) {
		eeprom_set_byte(EMLINKSOFFSET + x * 5 + 0, links[x][0]);
		eeprom_set_byte(EMLINKSOFFSET + x * 5 + 1, links[x][1]);
		eeprom_set_byte(EMLINKSOFFSET + x * 5 + 2, links[x][2]);
		eeprom_set_byte(EMLINKSOFFSET + x * 5 + 3, '-');
		eeprom_set_byte(EMLINKSOFFSET + x * 5 + 4, '-');
	}

	// Writing the on-board test program (in future versions it will be programmable)
	byte prog[] = { 0,
		INS_SET, 8, 1,
		INS_SET, 21, 5,
		INS_DECREASE, 21, 1,
		INS_JIF_ISZERO, 21, 15,
		INS_JMP, 7,
		INS_SET, 8, 0,
		INS_END, 0, 0, 0, 0
	};
	for (x = 0; x < sizeof(prog); x++) {
		eeprom_set_byte(EMFUNCSOFFSET + x, prog[x]);
	}

	return true;
}

/** Set a port's configuration (and name)
 \param id Port id
 \param cfg String with the configuration
 \param name New port name (optional)
 \return 0 if everything went right. Otherwise, -1
*/
int configPort(char id, const char *cfg, char *name)
{
	if (id < 0)
		return false;	// Ignoramos ID =-1 (puerto no encontrado)

	byte i = 0;
	if (cfg != NULL) {
		ports[(int)id].type = 'X';
		while (cfg[i] != 0) {
			switch (cfg[i]) {
			case 0:
			case 'x':
			case 'X':
			case '-':
				// Ignore (default=disabled)
				break;

			case 'p':
				pulseport = id;
				ports[(int)id].type = 'P';
				break;

			case 'A':
				alarmport = id;
				break;

			case 'd':
				ports[(int)id].type = 'D';
				break;

			case 'a':
				ports[(int)id].type = 'A';
				break;

			case 'i':
				if (ports[(int)id].type == 'D')
					ports[(int)id].type = 'd';
				if (ports[(int)id].type == 'A')
					ports[(int)id].type = 'a';
				if (ports[(int)id].type == 'P')
					ports[(int)id].type = 'p';
				break;

			case 'o':
				break;

#ifdef MODULE_DEBUG
			default:
				if (debug == 1)
					writef(output,
					       "%c:invalidcfg(%d) cfg:%s \n",
					       DEBUG, id, cfg);
#endif
			}
			i++;
		}
	}
	debug_write((int)id);
	debug_write(cfg);
	debug_write(ports[(int)id].type);

	if (name != NULL) {
		for (i = 0; i < 5; i++)
			eeprom_set_byte(EMPORTSLOT * id + i, name[i]);
	}

	return true;
}


/** Load the default configuration.
 Useful if the EEPROM was empty or corrupted or before loading a template.
*/
int loadDefaultConfig()
{
	char pname[6];
char buffer[10];
	strlcpy(pname, flstrn(str_empty,buffer,10), sizeof(pname));

	byte i;
	for(i=0;i<TOTALPORTS;i++) {
		if (i >= DIGITALPORTS + ANALOGPORTS) {	// Virtual ports
			itoan(i - (DIGITALPORTS + ANALOGPORTS), pname, 6);
			pname[0] = 'v';
			configPort(i, (char *)"-", pname);
		} else if (i < DIGITALPORTS) {	// Digital ports
			// Evitamos configurar los puertos de red
			//if (network!=0 && (i!=11 && i!=12 && i!=13)) {
			itoan(i, pname, 6);
			pname[0] = 'd';
			pname[1] = 'o';
			configPort(i, (char *)"do", pname);
			//}
		} else if ((i >= DIGITALPORTS) && (i < DIGITALPORTS + ANALOGPORTS)) {	// Analog:
			itoan(i - DIGITALPORTS, pname, 6);
			pname[0] = 'a';
			pname[1] = 'i';
			configPort(i, (char *)"-", pname);
#ifdef MODULE_RANGES
			ranges[i-DIGITALPORTS].alarmMin = 0;
			ranges[i-DIGITALPORTS].warningMin = 0;
			ranges[i-DIGITALPORTS].warningMax = 1023;
			ranges[i-DIGITALPORTS].alarmMax = 1023;
			ranges[i-DIGITALPORTS].tolerance = 1;
			// Conversion f(x) = A + Bx;
			ranges[i-DIGITALPORTS].fxA = 0;
			ranges[i-DIGITALPORTS].fxB = 1;
#endif
		} else {
//			Serial.print("D:error ");
			Serial.println(i);
		}
	}
	for (i=0;i<MAXLINKS;i++){
          emptyLink(i);	// No links in default configuration
        }

	configPort(0, (char *)"-", NULL);	// USB's RX
	configPort(1, (char *)"-", NULL);	// USB's TX
	eeprom_set_byte(EMNETCFOFFSET,0);
	ethSetIP(192, 168, 1, 177);
	ethSetMAC(192, 168, 1, 177);
	ethSetGW(192, 168, 1, 1);
	ethSetNetmask(255, 255, 255, 0);
	return true;
}

/// This function reads the command from the serial port
bool readFromSerialPort(char *ins)
{
	int b;
	byte i=0;
	// El dispositivo Arduino estara permanentemente iterando este bucle,
	// este o no conectado a traves del puerto USB. En el caso de que haya
	// conexion establecida, se tendra en cuenta.

	// Serial.available() devuelve el numero de bytes acumulados en el buffer.
	// Empleando un sistema de ancho fijo de los comandos facilitaremos en
	// gran medida el procesado
	while (Serial.available() > 0) {
		b = Serial.read();
		switch (b) {
		case -1:	// Null char
		case 0:	// Null char
		case 10:	// LF: No deberia suceder, salvo en consola emulada
			break;
		case 13:	// Final de linea
			output = SERIALPORT;
			//processInstruction(instruction);
			i = 0;
 			return true;
			break;
		case 8:	// Backspace
			if (i > 0) {
				i--;
				if (echo == 1)
					Serial.print(b);
			}
			ins[i] = 0;
			break;
		default:
			if (i < BUFFERSIZE) {
				//if (echo == 1)
				//Serial.print(b);
				ins[i] = b;
				i++;
				ins[i] = 0;
			} else {
//				output = SERIALPORT;
//				writef(output, flstr(strfmt_error2),
//				       ERROR, bname, 2);
				print_error(output,2);
			}
		}
	}
	return false;
}




/// Check if any port has changed its value, and trigger an event if required.
void refreshPortStatus()
{
	byte i = 0;
	byte k = 0;
	unsigned int val;
//	char pname[6];
//	static byte loops = 0;
	byte param1, param2, param3;
//	loops++;
//	if (loops >= 10)
//		loops = 0;	// Cada 10 loops

	for(i=0;i<TOTALPORTS;i++) {
		val = 0;
		if (ISVIRTUAL(i)) { // Virtual ports: special case
			param1 = eeprom_get_byte(EMPORTSOFFSET + i * EMPORTSLOT+ 16);
			param2 = eeprom_get_byte(EMPORTSOFFSET + i * EMPORTSLOT+ 17);
			param3 = eeprom_get_byte(EMPORTSOFFSET + i * EMPORTSLOT+ 18);
			if (param3 == INS_INCREASE) {
				ports[i].value = ports[param1].value + ports[param2].value;
 			}else if (param3 == INS_AND) {
				ports[i].value = ports[param1].value * ports[param2].value;
 			}else if (param3 == INS_OR) {
				ports[i].value = ports[param1].value + ports[param2].value;
 			}

		} else if (ISOUTPUT(i)) {
			if (ISDIGITAL(i))
				val = digitalRead(i);
			else
				val = ports[i].value;
			if (ports[i].value != val)
				triggerPortChange(i, ports[i].value, val);

		} else if (ISINPUT(i)) {
			if (ISDIGITAL(i)) {
				// varias lecturas para minimizar problemas de ruido
				val = HIGH;
				for (k = 0; k < 3; k++) {
					if (digitalRead(i) != HIGH) {
						val = LOW;
						break;
					}
					delay(10);
				}

				if (ports[i].value == LOW && val == HIGH) {
					triggerPortChange(i, ports[i].value,
							  HIGH);
					ports[i].value = HIGH;
				} else if (ports[i].value == HIGH && val == LOW) {
					triggerPortChange(i, ports[i].value,
							  LOW);
					ports[i].value = LOW;
				}
			}
			if (ISANALOG(i)) {
				// Las entradas analogicas tienen un rango distinto
#ifdef MODULE_RANGES
				val =
				    int ((2.5 *
					  (float)analogRead(i -
							    DIGITALPORTS) *
					  (float)ranges[i-DIGITALPORTS].fxB) /
					 1023.0) - ranges[i-DIGITALPORTS].fxA;
				ranges[i-DIGITALPORTS].acum_s += val; // Sumamos al acumulado del segundo
#else
				val =analogRead(i - DIGITALPORTS);
#endif

				//val=int(((float) analogRead(i-DIGITALPORTS) * (float) ranges[i-DIGITALPORTS].fxB * 2.5 / 1023.0 ) - ranges[i-DIGITALPORTS].fxA);
				triggerPortChange(i, ports[i].value, val);
				ports[i].value = val;
			}
			// Los puertos de entrada son susceptibles a sufrir ruido (verificar solo
			// durante los primeros 5s)
			if (millis() < 5000) {
				if (ports[i].changes >= MAXCHANGES) {
					// puerto ruidoso si hubo mas de MAXCHANGES cambios por segundo
					ports[i].type = 'N';	// Desactivado
//					eeprom_get_str(pname, i*EMPORTSLOT, 6);
//					writef(output, "%c:%s.%s noisy\n",
//					       WARNING, bname, pname);
				}
				ports[i].changes = 0;	// Ponemos a cero los contadores
			}
		}
	}
}

/// Send the list of links created to the standard output
void listLinks()
{
	byte i = 0;
	byte a = 0;
	char pname1[6];
	char pname2[6];
	char buffer[20];
	//char type = ' ';

	for (i = 0; i < MAXLINKS; i++) {
		// Los "slots" vacíos se almacenan como enlace "0-0"
		if ((links[i][0] != 0) && (links[i][1] != 0)) {
			eeprom_get_str(pname1, links[i][0]*EMPORTSLOT, 6);
			eeprom_get_str(pname2, links[i][1]*EMPORTSLOT, 6);
//                      strlcpy(lname, pname1, 5);
//                      strlcat(lname, "-", 15);
//                      strlcat(lname, pname2, 15);
//                      type = links[i][2];
			writef(output, flstrn(strfmt_lnk,buffer,20), INFO, bname, pname1,
			       pname2, (char*)links[i][2]);
			a++;
		}
		// Formato de salida:
		// 01234567890123456789012345 (byte nº)
		// I:port1-port2 type
	}
	if (a == 0) print_cmdok(output);
//		writef(output, "W:no links\n");
}

// {{{ listPorts(): Print the list of ports, including type and value
void listPorts()
{
	byte i;
//	int j;
	char buffer[20];
	char type[6];
	char value[6];
	char pname[6] = "";

	for(i=0;i<5;i++) type[i]='-';
	type[5]=0;
	if(output<HTTP) writef(output, "N:cmd ls    \n");
	for(i=0;i<TOTALPORTS;i++) {
		value[0] = 0;
		eeprom_get_str(pname, i*EMPORTSLOT, 6);
		pname[sizeof(pname) - 1] = 0;

		type[1] = ISINPUT(i)?'I':'O';

		if (ISDIGITAL(i)) {
  			type[0]='D';
			if (ports[i].value != LOW)
				strlcpy(value, "ON", sizeof(value));
			else
				strlcpy(value, "OFF", sizeof(value));
		}

		else if (ISANALOG(i)) {
  			type[0]='A';
			itoan(ports[i].value, value, sizeof(value));
		}

		else if (ISPULSE(i)) {
  			type[0]='P';
//		}
//		else if (ISVIRTUAL(i)) {
//			strlcpy(type, "V----", sizeof(type));
//			itoan(ports[i].value, value, sizeof(value));

		} else {
			type[0]='X';
			type[1]='-';
		}

		if (output == HTTP) {
#ifdef MODULE_WEBSERVER
			http_listPortsLine(pname, type, value);
#endif
		} else
			writef(output, flstrn(strfmt_std,buffer,20), INFO, bname, pname,
			       type, value);
	}

}

// }}}

/** Links two ports
 \param port1 Trigger port (usually input)
 \param port2 Triggered port (usually output)
 \return the link slot if the operation succeeded, otherwise -1 or -2 (invalid ports)
*/
byte addLink(byte port1, byte port2)
{
	byte l;
	if ((port1 == -1) || (port2 == -1))
		return -2;

	if ((ISINPUT(port1) && ISOUTPUT(port2)) || ISVIRTUAL(port1)) {
		for (l=0;l<MAXLINKS;l++) {
			if ((links[l][0] == 0) && (links[l][1] == 0))	// If the slot is empty
			{
				links[l][0] = port1;
				links[l][1] = port2;
				return l;
			}
			if ((links[l][0] == port1) && (links[l][1] == port2))	// the link already exists
			{
				return l;
			}
		}
	}
	return false;
}

/** Delete the link between two ports.
 \param port1 Trigger port
 \param port2 Triggered port
 \return 0 if everything went right, otherwise -1
*/
byte delLink(byte port1, byte port2)
{
	byte l;
	if ((port1 == -1) || (port2 == -1))
		return -2;
	for (l=0;l<MAXLINKS;l++) {
		if ((links[l][0] == port1) && (links[l][1] == port2)) {
			links[l][0] = 0;
			links[l][1] = 0;
			links[l][2] = 0;
			return 0;
		}
	}
	return -1;		// Enlace no encontrado
}

/** \page VPorts Virtual Ports
 The virtual ports are events that can be used as regular ports, linked to
 output ports to change their values to HIGH when the events start, bringing
 it to LOW when it's ended.

 The virtual ports can be used also as internal registers, storing the data
 that we want to preserve during the execution.

 All the possible uses for this ports will be developed in the near future.
*/

/** Get the specified port index
 \param label Name of the port
 \return The port's index, or -1 if not found
*/
byte getPortId(char *label)
{
	byte i;
	char pname[6];
	for(i=0;i<TOTALPORTS;i++) {
		eeprom_get_str(pname, i*EMPORTSLOT, 6);
		if (strncmp(pname, label, 5) == 0)
			return i;
	}
	print_error(output,101);
	return -1;
}

/// Initialises the Ethernet Shield
int initNetwork()
{
	/** \page Ethernet Ethernet Shield
	If the first byte of the network configuration has the integer value "1",
	we will proceed to initialise the Ethernet shield and start the network
	services.

	Since Arduino's Ethernet library does not support DHCP, the IP address,
	net mask, gateway and MAC address will have to be manually assigned. To do
	so, it will be necessary to connect to the Arduino board by the USB port,
	and setting those values with the command "eth".

	Once configured and activated, the board can be unplugged and the shield
	can be placed on the main board. When the board is powered again, the
	Ethernet interface should be available and waiting for connection.

	The network service provided is a listening port 23 that,
	once connected, will be used as the standard output.
	*/
	byte i;
	byte mac[6];
	byte ip[4];
	char disabled[2]="-";
	//! @todo: enable gateway, net mask and multicast address
	//byte gw[4];
	//byte mask[4];

	//if (ER(EMNETCFOFFSET) == 1) {	// Si el byte esta a 1, arrancamos la red
	//	network = 1;s
	//	delay(100);
		configPort(04, disabled, NULL);
#ifdef MEGA
		configPort(50, disabled, (char *)"smiso");
		configPort(51, disabled, (char *)"smosi");
		configPort(52, disabled, (char *)"sclck");
		configPort(53, disabled, (char *)"ssele");
#else
		configPort(10, disabled, NULL);
		configPort(11, disabled, NULL);
		configPort(12, disabled, NULL);
		configPort(13, disabled, NULL);
#endif
		eeprom_get_str((char*)mac, EMNETCFOFFSET + 1, 6);
		eeprom_get_str((char*)ip, EMNETCFOFFSET + 7, 4);
#if defined(MODULE_WEBSERVER) || defined(MODULE_TELNET)
		Ethernet.begin(mac, ip);
		//EEPROM2string(EMNETCFOFFSET+10,4,gw);
		//EEPROM2string(EMNETCFOFFSET+14,4,mask);
		delay(100);
#endif
		Serial.print("D:");
		for (i = 0; i < 6; i++)
			Serial.print(mac[i], HEX);
		Serial.println();
		Serial.print((char *)"D:");
		for (i = 0; i < 4; i++)
			Serial.print(ip[i], DEC);
		Serial.println(); 

//		Serial.println("I:nicok");
		return 0;
}

/** Save the MAC into the EEPROM
 \param macb3 Byte 3 of MAC Address
 \param macb4 Byte 4 of MAC Address
 \param macb5 Byte 5 of MAC Address
 \param macb6 Byte 6 of MAC Address
*/
int ethSetMAC(byte macb3, byte macb4, byte macb5, byte macb6)
{
	eeprom_set_byte(EMNETCFOFFSET + 1, 25);	// First
	eeprom_set_byte(EMNETCFOFFSET + 2, 25);	// and second bytes are fixed
	eeprom_set_byte(EMNETCFOFFSET + 3, macb3);
	eeprom_set_byte(EMNETCFOFFSET + 4, macb4);
	eeprom_set_byte(EMNETCFOFFSET + 5, macb5);
	eeprom_set_byte(EMNETCFOFFSET + 6, macb6);
	return true;
}

/** Save the IP address into the EEPROM
 \param ipb1 Byte 1 of IP Address
 \param ipb2 Byte 2 of IP Address
 \param ipb3 Byte 3 of IP Address
 \param ipb4 Byte 4 of IP Address
*/
int ethSetIP(byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
	eeprom_set_byte(EMNETCFOFFSET + 7, ipb1);
	eeprom_set_byte(EMNETCFOFFSET + 8, ipb2);
	eeprom_set_byte(EMNETCFOFFSET + 9, ipb3);
	eeprom_set_byte(EMNETCFOFFSET + 10, ipb4);
	return true;
}

/** Save the IP address of the gateway into the EEPROM
 \param ipb1 Byte 1 of IP Address
 \param ipb2 Byte 2 of IP Address
 \param ipb3 Byte 3 of IP Address
 \param ipb4 Byte 4 of IP Address
*/
int ethSetGW(byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
	eeprom_set_byte(EMNETCFOFFSET + 11, ipb1);
	eeprom_set_byte(EMNETCFOFFSET + 12, ipb2);
	eeprom_set_byte(EMNETCFOFFSET + 13, ipb3);
	eeprom_set_byte(EMNETCFOFFSET + 14, ipb4);
	return true;
}

/** Save the network mask into the EEPROM
 \param ipb1 Byte 1 of mask
 \param ipb2 Byte 2 of mask
 \param ipb3 Byte 3 of mask
 \param ipb4 Byte 4 of mask
*/
int ethSetNetmask(byte ipb1, byte ipb2, byte ipb3, byte ipb4)
{
	eeprom_set_byte(EMNETCFOFFSET + 15, ipb1);
	eeprom_set_byte(EMNETCFOFFSET + 16, ipb2);
	eeprom_set_byte(EMNETCFOFFSET + 17, ipb3);
	eeprom_set_byte(EMNETCFOFFSET + 18, ipb4);
	return true;
}

/** Sub-procedure for dealing with Ethernet board
 \param cmd Command
 \return
*/
int ethControl(const char *cmd)
{
  
	if (cmd[4] == 'u' && cmd[5] == 'p') {	// Activating ethernet
		eeprom_set_byte(EMNETCFOFFSET,1);
	} else if (cmd[4] == 's' && cmd[5] == 't') {	// Status of the ethernet
//		writef(output, flstr(strfmt_ethip), INFO, bname);
//		writef(output, flstr(strfmt_ethmc), INFO, bname);
//		writef(output, flstr(strfmt_ethmk), INFO, bname);
//		writef(output, flstr(strfmt_ethgw), INFO, bname); 
		return true;
	} else if (cmd[4] == 'm' && cmd[5] == 'c') {	// setting MAC address
		return ethSetMAC(h2d(cmd[7], cmd[8]), h2d(cmd[9], cmd[10]),
				 h2d(cmd[11], cmd[12]), h2d(cmd[13], cmd[14]));
	} else if (cmd[4] == 'i' && cmd[5] == 'p') {	// setting IP
		return ethSetIP(h2d(cmd[7], cmd[8]), h2d(cmd[9], cmd[10]),
				h2d(cmd[11], cmd[12]), h2d(cmd[13], cmd[14]));
	} else if (cmd[4] == 'g' && cmd[5] == 'w') {	// setting gateway
//      ethSetGW(h2d(larg[0],larg[1]), h2d(larg[2],larg[3]),
//              h2d(larg[4],larg[5]), h2d(larg[6],larg[7]));
	} else if (cmd[4] == 'n' && cmd[5] == 'm') {	// setting netmask
//      ethSetNetmask(h2d(larg[0],larg[1]), h2d(larg[2],larg[3]),
//              h2d(larg[4],larg[5]), h2d(larg[6],larg[7]));
	} else if (cmd[4] == 'o' && cmd[5] == 'f') {	// deactivating ethernet
		eeprom_set_byte(EMNETCFOFFSET,0);
	} else if (cmd[4] == 't' && cmd[5] == 'e') {	// testing ethernet
		// Serial.println("connecting...");
	}

	return true;
}
// }}}

// {{{
byte printRanges(char * port){
	char buffer[16];
	char format[16];
	byte portID = getPortId(port);
#ifdef MODULE_RANGES
	if (portID < DIGITALPORTS || portID > DIGITALPORTS + ANALOGPORTS) return 101;
	flstrn(strfmt_std_num,format,16);
	writef(output, format, INFO, bname, port,
	       flstrn(str_almin,buffer,16), ranges[portID-DIGITALPORTS].alarmMin);
	writef(output, format, INFO, bname, port,
	       flstrn(str_wnmin,buffer,16), ranges[portID-DIGITALPORTS].warningMin);
	writef(output, format, INFO, bname, port,
	       flstrn(str_wnmax,buffer,16), ranges[portID-DIGITALPORTS].warningMax);
	writef(output, format, INFO, bname, port,
	       flstrn(str_almax,buffer,16), ranges[portID-DIGITALPORTS].alarmMax);
	writef(output, format, INFO, bname, port,
	       flstrn(str_toler,buffer,16), ranges[portID-DIGITALPORTS].tolerance);
	writef(output, format, INFO, bname, port,
	       "f(x)a", ranges[portID-DIGITALPORTS].fxA);
	writef(output, format, INFO, bname, port,
	       "f(x)b", ranges[portID-DIGITALPORTS].fxB);
	return true;
#else
 return false;
#endif
}
// }}}

/// {{{
int groupPorts(char* port1, char* port2){
	byte i;
	byte port1_id = getPortId(port1);
	byte port2_id = getPortId(port2);
	short pos;
	char buffer[16];
	if(port1_id<0 || port2_id<0) return 101;
	for(i=ANALOGPORTS+DIGITALPORTS;i<TOTALPORTS;i++){
		pos = EMPORTSOFFSET + i * EMPORTSLOT;
		if (eeprom_get_byte(pos + 18) > 30 && eeprom_get_byte(pos + 18) < 200) {
			eeprom_set_byte(pos + 16,port1_id);
			eeprom_set_byte(pos + 17,port2_id);
			eeprom_set_byte(pos + 18,INS_INCREASE);
			ports[i].type = 'a'; //Enable the virtual port
			writef(output, flstrn(strfmt_lnk,buffer,16), 
				INFO, bname, port1,port2,ports[i].type);
			return true;
		}
	}
	return 501;
}
// }}}

// {{{
byte setOperation(byte portID, char* operation)
{
	short pos = EMPORTSOFFSET + portID * EMPORTSLOT+ 18;
	if ((portID > DIGITALPORTS + ANALOGPORTS) && (portID<TOTALPORTS)) {
		switch (operation[0]){
  			case 'A':
				if (operation[1]=='N') eeprom_set_byte(pos,INS_AND);
				if (operation[1]=='D') eeprom_set_byte(pos,INS_INCREASE);
				break;
			case 'O':
				if (operation[1]=='R') eeprom_set_byte(pos,INS_OR);
				break;
			default:
				return 203;
		}
		return true;
	} 
	else
	{
  		return 101;
	}
	return true;
}
/// }}}

/** Print the contents of EEPROM (serial port only). See \ref EEPROM Map
*/
void printMap()
{
	int i;
	int j;
	int p;
	byte b;

	Serial.print("0");
	for (i = 0; i < EMSEGMENTS; i++)	// For each segment in EEPROM
	{
		if (i * j < 260)
			Serial.print("0");
		Serial.print(i * j, HEX);
		Serial.print(":");
		if (i * j + 20 < 260)
			Serial.print("0");
		Serial.print(i * j + 19, HEX);
		Serial.print(" | ");
		for (j = 0; j < 20; j++) {
			p = i * 20 + j;
			b = eeprom_get_byte(p);
			if ((b > 32) && (b < 127))
				Serial.print(b, BYTE);
			else
				Serial.print(".");
			// Cada 5 Bytes un separador
			if (((j + 1) % 5) == 0)
				Serial.print(" ");
		}
		Serial.print(" | ");
		for (j = 0; j < 20; j++) {
			p = i * 20 + j;
			b = eeprom_get_byte(p);
			if (b < 16)
				Serial.print("0");
			Serial.print(b, HEX);
			// Cada 5 Bytes un separador
			if (((j + 1) % 5) == 0)
				Serial.print(" ");
		}
		Serial.println();
	}
}

/// Executes the next instruction of the \ref Func, if it exists
void executeNextInstruction()
{
	byte ins;
	byte arg1;
	byte arg2;

	if (regP == 0)
		return;
	ins = eeprom_get_byte(EMFUNCSOFFSET + regP);
	arg1 = eeprom_get_byte(EMFUNCSOFFSET + regP + 1);
	arg2 = eeprom_get_byte(EMFUNCSOFFSET + regP + 2);

	debug_write(ins);
	debug_write(arg1);
	debug_write(arg2);

	switch (ins) {
	case INS_END:
		regP = 0;
		return;
		break;
	case INS_SET:
		setPortValue(arg1, arg2);
		regP += 3;
		break;
	case INS_DECREASE:
		if (ports[arg1].value > 0)
			setPortValue(arg1, ports[arg1].value - arg2);
		regP += 3;
		break;
	case INS_INCREASE:
		if (ports[arg1].value < 255)
			setPortValue(arg1, ports[arg1].value + arg2);
		regP += 3;
		break;
	case INS_JMP:
		regP = arg1;
		break;
	case INS_JIF_ISZERO:
		if (ports[arg1].value == 0)
			regP = arg2;
		else
			regP += 3;	// otherwise, go to the next instruction
		break;
	case INS_JIF_NOTZERO:
		if (ports[arg1].value != 0)
			regP = arg2;
		else
			regP += 3;	// otherwise, go to the next instruction
		break;
	case INS_JIF_GT:
		if (regM > arg1) 
			regP = arg2;
		else
			regP +=3 ;
		break;
	case INS_JIF_LT:
		if (regM < arg1) 
			regP = arg2;
		else
			regP +=3 ;
		break;
	case INS_STORE:
		regM = arg1;
		break;
	default:
		break;
	}

}

/** Executes the specified command
 \param cmd The command string (max 20Bytes)
 \return 0 if the operation was successful, other if not
*/
int processInstruction(const char *cmd)
{
	int i;
	byte j;
//char buffer[10];
	char arg1[6];		// First argument (if specified)
	char arg2[6];		// Second argument (if specified)
	int code = 0;

	//! @todo Change "code" to zero if invalid char
	code = (cmd[0] - 96) << 10;
	code += (cmd[1] - 96) << 5;
	code += (cmd[2] - 96);

	/* All the commands will follow a similar structure:
	 * 012345678901234 <-byte no.
	 cmd arg01 arg02
	 cfg portn di
	 lbl portn alias
	 lnk portn portm
	 wmx portn value
	 wmn portn value
	 set portn value
	 */
	GETARG1(cmd, arg1);
	GETARG2(cmd, arg2);

#ifdef MODULE_DEBUG
	if (debug == 1) {
		writef(output, "D:Command %s encoded as %d \n", cmd, code);
		writef(output, "D:Cmd=%s, arg1=%s, arg2=%s \n", cmd, arg1,
		       arg2);
	}
#endif

	/// Supported commands:
	switch (code) {
	case 0:
		break;

	case CMD_LST:		/// - lst: List configured ports
		listPorts();
		return 0;

	case CMD_ECH:		/// - echo: (de)activates local echo
		echo = ! echo;
		return 0;

	case CMD_DEB:		/// - debug: (de)activates debug mode
		debug = ! debug;
		return 0;

	case CMD_DEF:		/// - default: load default configuration
		loadDefaultConfig();
		resetPorts();
		print_cmdok(output);
		//writef(output, flstr(strfmt_ncfdef), bname);
		return 0;

	case CMD_LOA:		/// - load: load stored configuration
		if (loadConfig()){
			print_cmdok(output);
//writef(output, flstrn(strfmt_cfloa,buffer,10), NOTICE, bname);
		}else{
			print_error(output,21);
		}	//writef(output, flstr(strfmt_error2), ERROR, bname, 21);
		return 0;

	case CMD_SAV:		/// - save: save configuration to EEPROM
		if (saveConfig()) {
			print_cmdok(output);
			//writef(output, flstr(strfmt_cmdok2), NOTICE, bname);
		} else {
			print_error(output,22);
 		}
			//writef(output, flstr(strfmt_error2), ERROR, bname, 22);
		return 0;

	case CMD_CFG:		/// - cfg: configures the specified port
		i = getPortId(arg1);
		if (configPort(i, arg2, NULL)) 	print_cmdok(output);
			//writef(output, flstr(strfmt_cmdok2), NOTICE, bname);
		resetPorts();
		return 0;

	case CMD_LBL:		/// - lbl: set an alias to a port
		i = getPortId(arg1);
		if (configPort(i, NULL, arg2))	print_cmdok(output);
			//writef(output, flstr(strfmt_cmdok2), NOTICE, bname);
		return 0;

	case CMD_RES:		/// - reset: clear the EEPROM
		eeprom_reset();
		print_cmdok(output);
		//writef(output, flstr(strfmt_cmdok2), NOTICE, bname);
		return 0;

	case CMD_MAP:		/// - map: print the \ref EEPROM
		printMap();
		return 0;

	case CMD_PUT:		/// - put: changes a Byte in the \ref EEPROM
		i = atoi(arg1);
		j = atoi(arg2);
		if (i > 0 && i < EMSEGMENTS * EMPORTSLOT && j >= 0 && j <= 255) {
			eeprom_set_byte(i, j);
			print_cmdok(output);
			//writef(output, flstr(strfmt_cmdok2), NOTICE, bname);
		} else
			print_error(output,30);
			//writef(output, flstr(strfmt_error2), ERROR, bname, 30);
		return 0;

	case CMD_VER:		/// - ver: display the current version
		printVersion();
		return 0;

	case CMD_SNM:		/// - snm: set a new name for the board
		saveBoardName(arg1);
		print_cmdok(output);
		//writef(output, flstr(strfmt_cmdok2), NOTICE, bname);
		return 0;

#ifdef MODULE_RANGES
	case CMD_WMX:		/// - wmx: set max warning for a analog port
		i = getPortId(arg1);
		ranges[i-DIGITALPORTS].warningMax = atoi(arg2);
		print_cmdok(output);
		return 0;

	case CMD_WMN:		/// - wmn: set min warning for a analog port
		i = getPortId(arg1);
		ranges[i-DIGITALPORTS].warningMin = atoi(arg2);
		print_cmdok(output);
		return 0;

	case CMD_AMX:		/// - amx: set max alarm for a analog port
		i = getPortId(arg1);
		ranges[i-DIGITALPORTS].alarmMax = atoi(arg2);
		print_cmdok(output);
		return 0;

	case CMD_AMN:		/// - amn: set min alarm for a analog port
		i = getPortId(arg1);
		ranges[i-DIGITALPORTS].alarmMin = atoi(arg2);
		print_cmdok(output);
		return 0;

	case CMD_TOL:		/// - tol: set tolerance for an analog port
		i = getPortId(arg1);
		ranges[i-DIGITALPORTS].tolerance = atoi(arg2);
		print_cmdok(output);
		return 0;

	case CMD_RNG:		/// - rng: show ranges for an analog port
		i = printRanges(arg1);
		if (i!=true) print_error(output,i);
		return 0;

	case CMD_FXA:		/// - fxa: assign the A-value of the \ref conversion
		i = getPortId(arg1);
		ranges[i-DIGITALPORTS].fxA = atoi(arg2);
		print_cmdok(output);
		return 0;

	case CMD_FXB:		/// - fxb: assign the B-value of the \ref conversion
		i = getPortId(arg1);
		ranges[i-DIGITALPORTS].fxB = atoi(arg2);
		print_cmdok(output);
		return 0;
#endif

	case CMD_GRP: 		/// - grp: group 2 ports into a virtualport
		i = groupPorts(arg1,arg2);
		if(i==true)
			print_cmdok(output);
		else
			print_error(output,i);
		return false;

	case CMD_SOP:		/// - sop: Set operation to grouped port
		i = getPortId(arg1);
		if (true==setOperation(getPortId(arg1),arg2))
			print_cmdok(output);
		return 0;

	case CMD_SET:		/// - set: set a value to an output port
		i = getPortId(arg1);
		if (arg2[1] == 'n')
			setPortValue(i, HIGH);
		else if (arg2[1] == 'f')
			setPortValue(i, LOW);
		else
			setPortValue(i, atoi(arg2));
		print_cmdok(output);
		return 0;

	case CMD_LNK:		/// - lnk: link two ports
		i = getPortId(arg1);
		j = getPortId(arg2);
		lastlink = addLink(i, j);
		if (lastlink >= 0)
			print_cmdok(output);
		else
			print_error(output,306);
			//writef(output, flstr(strfmt_error2), ERROR, bname, 306);
		return 0;

	case CMD_UNL:		/// - unl: unlink two ports
		i = getPortId(arg1);
		j = getPortId(arg2);
		if (delLink(i, j) == 0) print_cmdok(output);
		//	writef(output, flstr(strfmt_cmdok2), NOTICE, bname);
		return 0;
#ifdef MODULE_HIBERNATE
	case CMD_HIB:		/// - hib: hibernate
		writef(output, flstr(strfmt_hiber), NOTICE, bname);
		delay(100);
		goToSleep();
		return 0;
#endif
	case CMD_LLN:		/// - lln: list the current links
		listLinks();
		return 0;

	case CMD_LCF:		/// - lcf: configure the specified link
		if (lastlink < MAXLINKS) {
			debug_write(arg1);
			debug_write(lastlink);
			links[lastlink][2] = arg1[0];
			return 0;
		} else {
			print_error(output,305);
			//writef(output, flstr(strfmt_error2), ERROR, bname, 305);
			return false;
		}

	case CMD_UPT:		/// - upt: print uptime
		printUptime();
		return 0;

	case CMD_TIM:		/// - tim: print current time
		printDatetime();
		return 0;

	case CMD_MEM:		/// - mem: print available memory
		printMemory();
		return 0;

	case CMD_EXE:		/// - exe: call sub-procedure
		regP = atoi(arg1);
		if (regP > 0) print_cmdok(output);
		return 0;

	case CMD_TST:		/// - tst: perform a board test
		regP = 1;
		return 0;

#ifdef MODULE_WEBSERVER
	case -26476:
		//processHTTPQuery(cmd);
		return 0;

	case CMD_CSS:
		selectCSSFile(arg1);
		return 0;
#endif

	case CMD_ETH:		/// - eth: ethernet functions
		if (ethControl(cmd))
			print_cmdok(output);
		else
			print_error(output,440);
			//writef(output, flstr(strfmt_error2), ERROR, bname, 440);
		return 0;

#ifdef MODULE_ROUTINES
	case CMD_SUB:		/// - sub: create a subroutine
		if (createSubRoutine(arg1))
			print_cmdok(output);
		else
			print_error(output,445);
			//writef(output, flstr(strfmt_error2), ERROR, bname, 445);
		return 0;
#endif
	default:
		print_error(output,1);
		print_error(output,code);
		//writef(output, flstr(strfmt_error2), ERROR, bname, 1);
		return 1;
	}
}

// {{{
void updateTimeVars(){
	unsigned long time;	///< Internal variable for measuring time
	byte i;
	// Leemos las marcas de tiempo y actualizamos valores
	time = millis() / 1000;
	if (seconds != int(time % 60)) {	// Solamente actualizamos si ha cambiado el segundo
		seconds = time % 60;
		if (seconds == 0 && millis()>1000)
			minutes++;

		if (minutes == 60) {
			minutes=0;
			hours++;
		}
		if (hours == 24) {
			hours =0;
			days++;
  		}


#ifdef MODULE_RANGES
		//Serial.print("D:avgs ");
		for (i=0;i<ANALOGPORTS;i++){
			ranges[i].acum_m += int(ranges[i].acum_s / cps);
			//Serial.print(ranges[i].acum_s / cps);
			//Serial.print(";");
			ranges[i].acum_s = 0;
		}
		//Serial.println();
		cps = 0;
//#ifdef MODULE_SD
		// Guardamos promedios de los datos analogicos cada minuto
		if (seconds == 0  && millis()>1000){
			//Serial.print("D:avgM=");
			for (i=0;i<ANALOGPORTS;i++){
				// Guardamos la media del minuto
				//Serial.print(int(ranges[i].acum_m/60));
				//Serial.print(";");
// 				writeLog("minavg.log",ranges[i].acum_m/60); 
 				ranges[i].acum_h += int(ranges[i].acum_m / 60);
 				ranges[i].acum_m = 0; // Reinicializamos media
			}
			//Serial.println();
			//writeLog("test.log", "\n");
		}

		if (minutes == 0  && millis()>1000){ 
  			for (i=0;i<ANALOGPORTS;i++){
				// Guardamos la media de la hora
// 				writeLog("minavg.log",ranges[i].acum_h/60); 

				ranges[i].acum_h = 0; // Reinicializamos media
			}
#ifdef MODULE_LITE
			// If this is a "Lite" device, store the first Vport in EEPROM
			eeprom_set_ushort(960 + 2 * hours, ports[DIGITALPORTS+ANALOGPORTS].value);

#endif
			//Serial.println();
			//writeLog("test.log", "\n");
		}
#endif

#ifdef MODULE_TELNET
		// Enviamos broadcast UDP cada 10 segundos
		if ( eeprom_get_byte(EMNETCFOFFSET)>0){
			if (seconds % 10 == 0 ){
				sendOpenDomoAnnouncement();
			}
		}
#endif
		// Contamos los cambios para los puertos digitales y analogicos
		for (i=0;i<DIGITALPORTS+ANALOGPORTS;i++){
			ports[i].changes = 0;	// Ponemos a cero los contadores
			// Aqui podemos disparar eventos si ciertos puertos digitales
			// registran multiples cambios en un segundo
		}
	}
}
// }}}

/** Initializes the device.
 This function is called when the first power supply is connected.
*/
void setup()
{
	const char *disabled="-";

#if defined(MODULE_TELNET) || defined(MODULE_WEBSERVER)
	byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
	byte ip[] = { 192,168,1, 177 };
	byte gw[] = { 192,168,1, 1 };
	byte timeServer[] = {192, 43, 244, 18}; // time.nist.gov NTP server
#endif

//	unsigned char i;
	output = SERIALPORT;
	Serial.begin(9600);
	//Serial.println("D:init");
	// Proceso de carga de la configuración:

#ifdef MODULE_SD
	// Miramos si hay una MicroSD/SD conectada y si dispone de configuración
	if (eeprom_get_byte(EMBOARDOFFSET+6)!=0){
 		Serial.println("D:loadsd");
		if(false==loadConfigurationFromSD("data.cfg")){
  			print_error(output,21);
		}
	}

#endif

	// 2. Intentamos cargar la configuración almacenada
	if (loadConfig()) {
	//	Serial.println("D:loadcf");
	} else {
		//writef(SERIALPORT, flstr(strfmt_error2), ERROR, bname, 21);
		print_error(output,21);
		// 3. Si no hay ninguna válida, aplicamos configuración por defecto
		loadDefaultConfig();
	} 

//	Serial.print("D:mem ");
//	Serial.println(freeMemory());
//Serial.println(freeMalloc());

	byte svc = eeprom_get_byte(EMNETCFOFFSET);
	if (0<svc) {

#if defined(MODULE_TELNET) || defined(MODULE_WEBSERVER)
		ip[0]=eeprom_get_byte(EMNETCFOFFSET+7);
		ip[1]=eeprom_get_byte(EMNETCFOFFSET+8);
		ip[2]=eeprom_get_byte(EMNETCFOFFSET+9);
		ip[3]=eeprom_get_byte(EMNETCFOFFSET+10);

		gw[0]=eeprom_get_byte(EMNETCFOFFSET+11);
		gw[1]=eeprom_get_byte(EMNETCFOFFSET+12);
		gw[2]=eeprom_get_byte(EMNETCFOFFSET+13);
		gw[3]=eeprom_get_byte(EMNETCFOFFSET+14);
#endif

		configPort(4, disabled, NULL);// SD Select
#ifdef MEGA
		configPort(50, disabled, NULL);
		configPort(51, disabled, NULL);
		configPort(52, disabled, NULL);
		configPort(53, disabled, NULL);
#else
		configPort(10, disabled, NULL);
		configPort(11, disabled, NULL);
		configPort(12, disabled, NULL);
		configPort(13, disabled, NULL);
#endif

#if defined(MODULE_TELNET) || defined(MODULE_WEBSERVER)
		if (freeMemory()>500 && ip[0]<250){
//			initNetwork();
			Ethernet.begin(mac, ip, gw);
#ifdef MODULE_TELNET
			initTelnetServer();
			Udp.begin(22537);
			if (synchronizeNTP(timeServer)) Serial.println("D:TOK");
#endif
#ifdef MODULE_WEBSERVER
			//initWebServer();
			Webserver.begin();
#endif
			Serial.println("D:OK");
		}else {
			print_error(output,333);
			eeprom_set_byte(EMNETCFOFFSET,0);
		}
#endif
	}


	// Iniciamos los puertos
//	Serial.print("D:mem ");
//	Serial.println(freeMemory());

	resetPorts();
	printVersion();
}


// {{{ loop(): main
/** Permanent loop.
 This is the main function of Arduino. This function is called again when
 the last call is ended.
*/
void loop()
{
 	char instruction[20]; 
 
	cps++; // Incrementamos el contador de ciclos por segundo

#ifdef MODULE_ODENERGY
	readEnergySensors();
#else
	refreshPortStatus(); // ¿Ha cambiado de valor algun puerto?
#endif
	updateTimeVars(); // Eventos temporizados

	if (readFromSerialPort(instruction))  // Leemos del puerto serie
	{
  		output = SERIALPORT;
		processInstruction(instruction);
	}
	if (seconds > 0){
#ifdef MODULE_TELNET
	if (readFromTelnetPort(instruction))
	{
		output = TELNET;
		processInstruction(instruction);
	}
#endif
#ifdef MODULE_WEBSERVER
	if(readFromHTTPPort(instruction)) 
	{
		//processHTTPQuery(instruction);
	}
#endif
	}
	// Si hay alguna dirección en regP, ejecutamos
	if (regP > 0)
		executeNextInstruction();



	// Tiempo de espera
	delay(DELAYCYCLE);
}

// }}}



#ifdef NO_PDE
// {{{ main(): entry point
int main()
{
	setup();
	for (;;)
		loop();
	return 0;
}

// }}}
#endif

